/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.circuit;

import java.io.PrintStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import com.cburch.logisim.comp.Component;

public class ReplacementMap {
    private boolean frozen;
    private HashMap<Component,HashSet<Component>> map;
    private HashMap<Component,HashSet<Component>> inverse;

    public ReplacementMap(Component oldComp, Component newComp) {
        this(new HashMap<Component,HashSet<Component>>(),
                new HashMap<Component,HashSet<Component>>());
        HashSet<Component> oldSet = new HashSet<Component>(3);
        oldSet.add(oldComp);
        HashSet<Component> newSet = new HashSet<Component>(3);
        newSet.add(newComp);
        map.put(oldComp, newSet);
        inverse.put(newComp, oldSet);
    }

    public ReplacementMap() {
        this(new HashMap<Component,HashSet<Component>>(),
                new HashMap<Component,HashSet<Component>>());
    }

    private ReplacementMap(HashMap<Component,HashSet<Component>> map,
            HashMap<Component,HashSet<Component>> inverse) {
        this.map = map;
        this.inverse = inverse;
    }

    public void reset() {
        map.clear();
        inverse.clear();
    }

    public boolean isEmpty() {
        return map.isEmpty() && inverse.isEmpty();
    }

    public Collection<Component> getReplacedComponents() {
        return map.keySet();
    }

    public Collection<Component> get(Component prev) {
        return map.get(prev);
    }

    void freeze() {
        frozen = true;
    }

    public void add(Component comp) {
        if (frozen) {
            throw new IllegalStateException("cannot change map after frozen");
        }
        inverse.put(comp, new HashSet<Component>(3));
    }

    public void remove(Component comp) {
        if (frozen) {
            throw new IllegalStateException("cannot change map after frozen");
        }
        map.put(comp, new HashSet<Component>(3));
    }

    public void replace(Component prev, Component next) {
        put(prev, Collections.singleton(next));
    }

    public void put(Component prev, Collection<? extends Component> next) {
        if (frozen) {
            throw new IllegalStateException("cannot change map after frozen");
        }

        HashSet<Component> repl = map.get(prev);
        if (repl == null) {
            repl = new HashSet<Component>(next.size());
            map.put(prev, repl);
        }
        repl.addAll(next);

        for (Component n : next) {
            repl = inverse.get(n);
            if (repl == null) {
                repl = new HashSet<Component>(3);
                inverse.put(n, repl);
            }
            repl.add(prev);
        }
    }

    void append(ReplacementMap next) {
        for (Map.Entry<Component,HashSet<Component>> e : next.map.entrySet()) {
            Component b = e.getKey();
            // what b is replaced by
            HashSet<Component> cs = e.getValue();
            // what was replaced to get b
            HashSet<Component> as = this.inverse.remove(b);
            // b pre-existed replacements so
            if (as == null) {
                // we say it replaces itself.
                as = new HashSet<Component>(3);
                as.add(b);
            }

            for (Component a : as) {
                HashSet<Component> aDst = this.map.get(a);
                // should happen when b pre-existed only
                if (aDst == null) {
                    aDst = new HashSet<Component>(cs.size());
                    this.map.put(a, aDst);
                }
                aDst.remove(b);
                aDst.addAll(cs);
            }

            for (Component c : cs) {
                // should always be null
                HashSet<Component> cSrc = this.inverse.get(c);
                if (cSrc == null) {
                    cSrc = new HashSet<Component>(as.size());
                    this.inverse.put(c, cSrc);
                }
                cSrc.addAll(as);
            }
        }

        for (Map.Entry<Component,HashSet<Component>> e : next.inverse.entrySet()) {
            Component c = e.getKey();
            if (!inverse.containsKey(c)) {
                HashSet<Component> bs = e.getValue();
                if (!bs.isEmpty()) {
                    //OK
                    System.err.println("internal error: component replaced but not represented");
                }
                inverse.put(c, new HashSet<Component>(3));
            }
        }
    }

    ReplacementMap getInverseMap() {
        return new ReplacementMap(inverse, map);
    }

    public Collection<Component> getComponentsReplacing(Component comp) {
        return map.get(comp);
    }

    public Collection<? extends Component> getRemovals() {
        return map.keySet();
    }

    public Collection<? extends Component> getAdditions() {
        return inverse.keySet();
    }

    public void print(PrintStream out) {
        boolean found = false;
        for (Component c : getRemovals()) {
            if (!found) {
                out.println("  removals:");
            }

            found = true;
            out.println("    " + c.toString());
        }
        if (!found) {
            out.println("  removals: none");
        }


        found = false;
        for (Component c : getAdditions()) {
            if (!found) {
                out.println("  additions:");
            }

            found = true;
            out.println("    " + c.toString());
        }
        if (!found) {
            out.println("  additions: none");
        }

    }
}
